package pars;

import java.util.*;
/*
import ast.Expr;
import ast.ExprBinary;
import ast.ExprBinary.Add;
import ast.ExprBinary.Sub;
import ast.ExprBinary.Mul;
import ast.ExprBinary.Div;
import ast.ExprBinary.Pow;
import ast.ExprUnary.Neg;
import ast.Num;
*/
import fun.grammar.Grammar;
import fun.grammar.Word;
import fun.parser.Tree;
import fun.parser.earley.EarleyParser;
import fun.parser.earley.EarleyParser.PostMortem;
import fun.parser.earley.EarleyState;

import lex.Token;


public class GayParser { // ;)
	/**QAQA
	 * all terminals must be Token's TAG, and not token's value.
	 * EPSILON - must be space.
	 * q means ? in regex
	 * LIBRARY grammer accepts not primitive types in its formals: static void func(int i, GayParser EyalGolan); 
	 * 
	 * You're shirt is gorgeous!
	 *   */
	String GRAMMAR =
			"S -> CLASSDECL*\n" +
			"CLASSDECL* -> CLASSDECL CLASSDECL* | EPSILON\n" +
			"CLASSDECL -> class CLASS_ID EXTEND_CLASSq { FIELD_OR_METHOD* }\n" +
			"EXTEND_CLASSq -> extends CLASS_ID | EPSILON\n" +
			
			"FIELD_OR_METHOD* -> FIELD_OR_METHOD FIELD_OR_METHOD* | EPSILON\n" +
			"FIELD_OR_METHOD -> FIELD | METHOD\n" +
			"FIELD -> FIELD_TYPE ID COMMA_ID* ;\n" +
			
			"FIELD_TYPE -> TYPE | FIELD_TYPE [ ]\n" +				
			"TYPE -> int | boolean | string | CLASS_ID\n" +
			
			"COMMA_ID* -> COMMA_ID COMMA_ID* | EPSILON\n" +
			"COMMA_ID -> , ID\n" +
			
			"METHOD -> TYPE_OR_VOID ID ( FORMALSq ) { STMT* } | static TYPE_OR_VOID ID ( FORMALSq ) { STMT* }\n" +
			"TYPE_OR_VOID -> FIELD_TYPE | void\n" +
		
			"FORMALSq -> FORMAL COMMAFORMAL* | EPSILON\n" + 
			"COMMAFORMAL* -> COMMAFORMAL COMMAFORMAL* | EPSILON\n" +
			"COMMAFORMAL -> , FORMAL\n" +
			"FORMAL -> FIELD_TYPE ID\n" +
		
			"EPSILON ->  \n" + 
			
			/*Statement logic*/
			"STMT* -> STMT STMT* | EPSILON\n" +				
			"STMT -> IF_OR_SIMPLE_STMT \n" +
			"SIMPLE_STMT -> LOCATION = EXPR ; | CALL ; | return EXPR ; | return ; "
					+ "| while ( EXPR ) STMT | break ; | continue ; | { STMT* } "
					+ "| FIELD_TYPE ID ; | FIELD_TYPE ID = EXPR ;\n" +		
			
			/*if logic*/
			"IF_OR_SIMPLE_STMT -> if ( EXPR ) IF_OR_SIMPLE_STMT | IF-ELSE_OR_SIMPLE_STMT\n" +
			"IF-ELSE_OR_SIMPLE_STMT -> if ( EXPR ) IF-ELSE_OR_SIMPLE_STMT else IF_OR_SIMPLE_STMT | SIMPLE_STMT\n" +

			/*Expression logic*/
			"EXPR ->  LOGIC_OR\n" +			
			
			/*LOGIC_OR - Defines operator precedence and associativity according to IC spec. */
			"LOGIC_OR -> LOGIC_AND || LOGIC_OR | LOGIC_AND\n" +
			"LOGIC_AND -> EQ_COMPARISON && LOGIC_AND | EQ_COMPARISON\n" +
			"EQ_COMPARISON -> RELOP == RELOP | RELOP != RELOP | RELOP\n" +
			"RELOP -> PLUS_MINUS < PLUS_MINUS | PLUS_MINUS <= PLUS_MINUS | PLUS_MINUS > PLUS_MINUS | PLUS_MINUS >= PLUS_MINUS | PLUS_MINUS\n" +
			"PLUS_MINUS -> PLUS_MINUS + MULT_DIV_REM | PLUS_MINUS - MULT_DIV_REM | MULT_DIV_REM\n" +
			"MULT_DIV_REM -> MULT_DIV_REM * UNOP | MULT_DIV_REM / UNOP | MULT_DIV_REM % UNOP | UNOP\n" +
			"UNOP -> - DOT_OR_PARA | ! DOT_OR_PARA | DOT_OR_PARA | NEW_TYPE\n" +   
			"NEW_TYPE -> new FIELD_TYPE [ EXPR ]\n" +
			"DOT_OR_PARA -> DOT_OR_PARA . length | DOT_OR_PARA . SIMPLECALL | DOT_OR_PARA . ID | DOT_OR_PARA [ EXPR ] | CLASS_ID . SIMPLECALL | ( EXPR ) | SIMPLE_EXPR\n" +
			"SIMPLE_EXPR -> ID | SIMPLECALL | this | new CLASS_ID ( ) | LITERAL\n" +
			
			"SIMPLECALL -> ID ( ) | ID ( EXPR COMMA_EXPR* )\n" +  
			"COMMA_EXPR* -> COMMA_EXPR COMMA_EXPR* | EPSILON\n" +
			"COMMA_EXPR -> , EXPR \n" +
			
			"LOCATION -> ID | EXPR . ID | EXPR [ EXPR ]\n" +
									
			"CALL -> CLASS_ID . SIMPLECALL | SIMPLECALL | EXPR . SIMPLECALL\n" +
			
			"LITERAL -> INTEGER | STRING | true | false | null\n"; 

			
	String LIB_GRAMMAR = 
			"S -> class CLASS_ID { LIBMETHOD* }\n" +
			"LIBMETHOD* -> LIBMETHOD LIBMETHOD* | EPSILON\n" +
			"LIBMETHOD -> static RETURN_VALUE ID ( FORMALSq ) ;\n" +
			
			"RETURN_VALUE -> FIELD_TYPE | void\n" +   
			
			"FIELD_TYPE -> TYPE | FIELD_TYPE [ ]\n" +				
			"TYPE -> int | boolean | string | CLASS_ID\n" +
			
			"FORMALSq -> FORMAL COMMAFORMAL* | EPSILON\n" + 
			"COMMAFORMAL* -> COMMAFORMAL COMMAFORMAL* | EPSILON\n" +
			"COMMAFORMAL -> , FORMAL\n" +
			"FORMAL -> FIELD_TYPE ID\n" +
		
			"EPSILON ->  \n";
			
			
			
			
			
	Grammar grammar;
	
	public GayParser()
	{
		grammar = new Grammar(GRAMMAR);
	}
	
	/** Parses the token list and returns a fun.parser.Tree instance.
	 * In case of successful parsing returns fun.parser.Tree.
	 * In case of a parsing error, prints error message and returns null
	 * */
	fun.parser.Tree parse(Iterable<Token> tokens)
	{
		EarleyParser e = new EarleyParser(tokens, grammar);
		System.out.println("***** e content: *****\n" + e ); //QAQQA
		List<EarleyState> pts = e.getCompletedParses();		
		System.out.println("***** pts size: " + pts.size()); //QAQQA
		
		//In case of 0 or more than 1 parse trees, deal with problem and exit program
		if (pts.size() != 1){
			
			//in case of error during parsing execution, print suitable error message
			if(pts.size() == 0){
				promptError(e);
				return null;
			}
			
			int i = pts.size();
			while(i>0){
				fun.parser.Tree qaTreeHugger = pts.get(i-1).parseTree();
				System.out.println("\n*****\n*****\n*****\n***** tree : \n" + qaTreeHugger.prettyPrint()); //QAQQA
				i--;
			}
			
			//Else, more than 1 tree is available. QAQA - should we return null instead?
			throw new Error("parse error"); /*QAQA WHAT?????*/
		}
		
		fun.parser.Tree qaTreeHugger = pts.get(0).parseTree();
		
		System.out.println("\n***** tree : \n" + qaTreeHugger.prettyPrint()); //QAQQA
		
		
		
		/*QAQA - Testing zone - INTEGER range*/
		/*
		System.out.println("CHECK INTEGER:...");
		for(Word word : e.getWords()){
			Token token = (Token)word;
			String tag = token.tag;
			if(tag.compareTo("INTEGER") == 0){
				String value = token.text;
				if(!IntegerInBounds(value)){
					System.out.println(token.line + ":" + token.start + ": syntax error; numeric literal out of range: " + value);
				} 
			}
		}
		
		if(!IntegerInBounds("-21474836458")){
			System.out.println(77 + ":" + 77 + ": syntax error; numeric literal out of range: " + "-21474836458");
		} 
		*/
		
		return pts.get(0).parseTree();
	}
	
	
	/**/
	private void promptError(EarleyParser e){

		/*Consider this: 
		 * (1) What happens in Library Grammar?
		 * (2) Special cases?
		 * */
		
		System.out.println("***** promptError\n"); //QAQQA
		
		//Need to report what diagnoseError returns
		PostMortem pm = e.diagnoseError();		
		
		//Get last accepted Token
		// Check if Token with instanceof(Token) or - QAQA
		Token errToken = (Token)pm.token;
		
		
		//Create expectedExpr string here
		String expectedExpr = new String();
		Iterator<String> expectedExpressionsIterator = pm.expecting.iterator();
		if(expectedExpressionsIterator.hasNext()){
			expectedExpr += "'" + expectedExpressionsIterator.next() + "'";
			
			while(expectedExpressionsIterator.hasNext()){
				expectedExpr += " or '" + expectedExpressionsIterator.next() + "'";
			}
		}
		
		//Check Token's validity
		if(!(errToken instanceof Token)){
			System.out.println("at end of input : syntax error; expected " + expectedExpr); 
			return;
		};
		
		//Print error
		System.out.println(errToken.line + ":" + errToken.start + 
									" : syntax error; expected " + expectedExpr +
									", but found " + "'" + errToken.tag + "'\n");	/*QAQA - Gil: fixed to errToken.tag instead of errToken.text */
	
	}
	
	/** Checks whether "value" as an Integer is in Integer's valid bounds: minimum value of -2^31 and a maximum value of 2^31-1.
	 * Returns true if value is in bounds and false if not in bounds.
	 * */
	private boolean IntegerInBounds(String value){
		
		try {   
			int number = Integer.parseInt(value);
		} catch (NumberFormatException nfe) {
		  // Handle the condition when str is not an Integer number.
			System.out.println("INTEGER is out of bounds!\n");
			return false;
		}
		
		return true;
	}
	
	
	
	/*Expr constructAst(fun.parser.Tree parseTree)
	{
		Word r = parseTree.root;
		fun.parser.Tree[] s = 
			parseTree.subtrees.toArray(new fun.parser.Tree[0]);
		 Branch according to root 
		switch (r.tag) {
		case "S":
		case "E":
		case "X":
		case "T":
			if (s.length == 1)
				return constructAst(s[0]);
			else {
				Expr a = constructAst(s[0]);
				Expr b = constructAst(s[2]);
				switch (s[1].root.tag)
				{
				case "+": return new Add(a, b);
				case "-": return new Sub(a, b);
				case "*": return new Mul(a, b);
				case "/": return new Div(a, b);
				case "^": return new Pow(a, b);
				default:
					throw new Error("internal error");
				}
			}
		case "F":
			switch (s[0].root.tag)
			{
			case "#": return constructAst(s[0]);
			case "-": return new Neg(constructAst(s[1]));
			default:  return constructAst(s[1]);
			}
		case "#": {
			Token tok = (Token)r;
			return new Num(Double.parseDouble(tok.text));
		}
		default:  should never get here 
			throw new Error("internal error");
		}
	}
	*/
	public fun.parser.Tree process(Iterable<Token> tokens)
	{
		return parse(tokens);
		//Pay attention: in case of parsing failure, parse returns 
		
		//return constructAst(parse(tokens));
	}
	/*
	public String infix(Expr ast)
	{
		String s = ast.accept(
			new Expr.Visitor() {
	
				@Override
				public Object visit(Neg e)
				{
					return "(-" + e.getOperand().accept(this) + ")";
				}
				
				@Override
				public Object visit(Pow e) { return join(e); }
				@Override
				public Object visit(Div e) { return join(e); }
				@Override
				public Object visit(Mul e) { return join(e); }
				@Override
				public Object visit(Sub e) { return join(e); }
				@Override
				public Object visit(Add e) { return join(e); }
	
				@Override
				public Object visit(Num e)
				{
					return e.eval();
				}
				
				protected String join(ExprBinary e)
				{
					Expr[] ab = e.getOperands();
					return "(" + ab[0].accept(this) + " " + e.getOperator() 
							+ " " + ab[1].accept(this) + ")";
				}
			}).toString();
		// chop ( )
		if (s.startsWith("(")) s = s.substring(1, s.length()-1);
		return s;
	}*/
}